package wsi.resolve;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.rygh.core.html.table.HtmlTable;
import org.rygh.core.html.table.TableData;
import org.rygh.core.html.table.TableTuple;
import org.rygh.core.model.common.MapList;
import org.rygh.core.model.common.MapSet;
import org.rygh.core.util.counter.SimpleCounter;
import org.rygh.core.xml.XmlPath;
import org.rygh.semantic.gui.swing.input.Input;
import org.rygh.semantic.model.ex.FactSet;
import org.rygh.semantic.model.ex.PredicatePath;
import org.rygh.semantic.model.rdf.Predicate;

import wsi.dataguide.DataGuide;
import wsi.dataguide.tree.WSXmlLeaf;
import wsi.fusion.FunctionalityFuser;
import wsi.fusion.Fuser;
import wsi.fusion.FusionTuple;
import wsi.fusion.ProbabilityFuser;
import wsi.fusion.VariableManager;
import wsi.match.MatchPair;
import wsi.match.MatchProperty;
import wsi.match.MatchProperty.Threshold;
import wsi.match.SubtreeController;
import wsi.match.SubtreeMatch;
import wsi.subgraph.tree.EntityBranch;
import wsi.subgraph.tree.EntityLeaf;
import wsi.subgraph.tree.EntityTree;

/**
 * The class which performs specific and concrete resolving procedures.
 * 
 * @author nanmus
 * 
 */
public class Resolver {
	enum FusionType {
		Functionality, Probability, WebBased
	}

	private final Fuser fuser;

	private final SimpleCounter<String> factItemCounter = new SimpleCounter<>();
	private final SimpleCounter<String> tempFactItemCounter = new SimpleCounter<>();

	private final MapSet<String, Input> hitInputMap = new MapSet<>();
	// private final List<FusionTuple> fusionResults = new ArrayList<>();

	private final DataGuide dataGuide;
	private final FusionType fusionType = FusionType.Probability;

	private SubtreeMatch subtreeMatch;
	private EntityTree entityTree;

	private EntityLeaf inputNode;
	private EntityLeaf centerNode;

	private MatchProperty matchProperty;
	private PredicatePath inputToCenterPath;

	/**
	 * counter is number of subtrees
	 */
	private int counter = 0;

	public Resolver(DataGuide dataGuide, SubtreeController subtreeController) {
		this.dataGuide = dataGuide;

		switch (fusionType) {
		case Functionality:
			fuser = new FunctionalityFuser(subtreeController);

			break;

		case Probability:
			fuser = new ProbabilityFuser(subtreeController);

			break;

		default:
			fuser = null;
			assert false;
			break;
		}
	}

	public void addUnmatchedItem(WSXmlLeaf leaf, Predicate predicate, int count) {
		VariableManager variableManager = fuser.getVariableManager();

		variableManager.register(leaf, false);

		FactItem item = new FactItem();
		item.setXmlLeaf(leaf);
		item.setInputToCenterPath(null);
		item.setCenterToValueBranch(null);
		item.setVariableManager(variableManager);
		item.setTempPredicate(predicate);
		item.compose(inputNode);

		tempFactItemCounter.count(item.getKey(), item);
		tempFactItemCounter.set(item.getKey(), count);
	}

	/**
	 * if both entity tree and xml tree contain/do not contain node and xpath of
	 * this pair, then we count as a match.
	 */
	public void compensate(MapList<Input, WSXmlLeaf> subrootMapList,
			Map<Input, EntityTree> entityTreeMap) {
		// log.info("compensating");

		List<WSXmlLeaf> good = new ArrayList<>();
		FactItem item;
		Set<Input> hitinputs;
		WSXmlLeaf dg_leaf;

		for (String key : factItemCounter.keySet()) {
			// log.info("compensate for " + key);
			/*
			 * many pairs may have the same key (xpath = predicate). here the
			 * node of this pair mp may belong to a certain entity tree.
			 */
			item = (FactItem) factItemCounter.getValue(key);

			dg_leaf = item.getXmlLeaf();
			hitinputs = hitInputMap.get(key);

			int compensate_hit = 0;

			for (Entry<Input, List<WSXmlLeaf>> e : subrootMapList.entrySet()) {
				Input input = e.getKey();

				// log.info("    test subrootMap input " + input);

				if (hitinputs.contains(input)) {
					// log.info("hit input");
					continue;
				}

				if (!entityTreeMap.containsKey(input)) {
					// log.info("No entity tree for " + input.getValue());
					continue;
				}

				EntityTree entityTree = entityTreeMap.get(input);

				if (entityTree.containsNodesAtPredicatePathOf(item)) {
					// log.info("contains path in entity tree for "
					// + item.getCenterToValulePath());
					continue;
				}

				//

				good.clear();

				if (matchProperty.isExistsMatch()) {
					int best_index = matchProperty.getSubtreeIndex();
					good.add(e.getValue().get(best_index - 1));

					assert good.get(0).getXpath().getLastIndex() == best_index : good
							.get(0).getXpath().getLastIndex();
				} else
					good.addAll(e.getValue());

				boolean mapped = false;

				for (WSXmlLeaf r : good)
					if (r.containsLeafEndsWith(dg_leaf.getXpath())) {
						mapped = true;
						break;
					}

				if (mapped) {
					// log.info("mapping found in xml tree for "
					// + item.getXmlLeaf().getXpath());
					continue;
				}

				compensate_hit++;
				// log.info("   compensate_hit @ input " + input + ": "
				// + compensate_hit);
			}

			if (compensate_hit > 0) {
				// log.info("   compensate_hit @ item " + key + ": "
				// + compensate_hit);
				factItemCounter.set(key,
						compensate_hit + factItemCounter.get(key));
			}
		}

		normalize();
	}

	public void generate() {
		assert centerNode != null;

		counter++;
		factItemCounter.setNormalizeFactor(counter);

		// System.out.printf("generate, matched pairs %d, grafted pairs %d.\n",
		// sm
		// .getMatchPairs().size(), sm.getGraftPairs().size());

		generate(subtreeMatch.getInput(), subtreeMatch.getMatchPairs());
		generate(subtreeMatch.getInput(), subtreeMatch.getGraftPairs());
	}

	private void generate(Input input, List<MatchPair> pairs) {
		fuser.prepareVariables(pairs, dataGuide);

		PredicatePath centerToValulePath;
		WSXmlLeaf dg_leaf, leaf;

		EntityBranch centerToValueBranch;
		EntityLeaf node;

		/*
		 * we assume that one subtree contains a unique key consider
		 * .../alias-list/alias[1]/@sort-name = u:label and
		 * .../alias-list/alias[2]/@sort-name = u:label. both positions contain
		 * similar values. "Ann Margaret" and "Ann-Margaret"
		 */
		Set<String> keySet = new HashSet<>();
		FactItem item;

		for (MatchPair pair : pairs) {
			// System.out.println("[A generateFactTemplate] MatchPair " + pair);

			if (keySet.contains(pair.getKey()))
				continue;

			keySet.add(pair.getKey());

			// System.out.println("[B generateFactTemplate] MatchPair " + pair);

			// records for a certain matched pair, the input values contribute
			// this pair, for compensation

			leaf = pair.getXmlLeaf();
			node = pair.getEntityLeaf();

			dg_leaf = dataGuide.getIsomorphicLeaf(leaf);
			assert dg_leaf != null : leaf.getXpath();
			assert dg_leaf != leaf;

			item = new FactItem();
			item.setXmlLeaf(dg_leaf);
			item.setInputToCenterPath(inputToCenterPath);

			centerToValueBranch = entityTree.link(centerNode, node);
			centerToValulePath = centerToValueBranch.getPredicatePath();
			assert centerToValulePath != null;

			if (centerToValulePath.containsMirroredRelations()) {
				// log.info(String.format("detected mirrored relations %s",
				// centerToValulePath.getKey()));
				continue;
			}

			item.setCenterToValueBranch(centerToValueBranch);
			item.setVariableManager(fuser.getVariableManager());
			item.compose(inputNode);

			// System.out.println("  fact item count " + item.getKey());

			factItemCounter.count(item.getKey(), item);
			hitInputMap.put(item.getKey(), input);
		}
	}

	public SimpleCounter<String> getFactItemCounter() {
		return factItemCounter;
	}

	public Fuser getFuser() {
		return fuser;
	}

	public MapSet<String, Input> getHitInputMap() {
		return hitInputMap;
	}

	public SimpleCounter<String> getTmpFactItemCounter() {
		return tempFactItemCounter;
	}

	public VariableManager getVariableManager() {
		return fuser.getVariableManager();
	}

	public void initFusion() {
		switch (fusionType) {
		case Functionality:
			((FunctionalityFuser) fuser).prepare(factItemCounter);

			break;

		default:
			break;
		}

	}

	public FactSet instantiate(Input input, WSXmlLeaf subroot) {
		// System.out.printf("to instantiate input %s subtree %s\n", input,
		// subroot.getXpath());

		assert input != null;
		assert subroot != null;

		if (subtreeMatch != null) {
			assert input.equals(subtreeMatch.getInput());
		}

		FactSet facts = new FactSet();

		fuser.setInput(input);

		switch (fusionType) {

		case Probability:

			assert subtreeMatch != null;
			assert entityTree != null;

			ProbabilityFuser pfuser = (ProbabilityFuser) fuser;
			FusionTuple ftuple = null;

			// System.out.println("entity tree root "
			// + entityTree.getConcreteRoot().getValue().getValue());

			pfuser.setSubtreeMatch(subtreeMatch);
			pfuser.setEntityTree(entityTree);

			final double threshold = Threshold.MAJORITY;

			if (pfuser.isInputEqualsToCenter()) {
				pfuser.setCenterNode(inputNode);

				// System.out.println("fuse to input node.");
				if (!subtreeMatch.isEmpty()) {

					double prob = ((ProbabilityFuser) fuser).compute(inputNode);

					if (prob > threshold) {
						ftuple = new FusionTuple(subroot, entityTree,
								inputNode, subtreeMatch);
						ftuple.setProbability(prob);
					} else {
						System.out.printf(
								"[warn] fuse to input [%s] small prob. %.2f\n",
								inputNode.getValue().getValue(), prob);
					}
				}
			} else {
				/*
				 * if it is a forall match, we have to first fuse which center
				 * node corresponds to this subtree.
				 */

				List<EntityLeaf> centers = inputNode
						.getChildren(inputToCenterPath);

				if (centers.isEmpty()) {
					System.out.printf(
							"[warn] no center nodes for input %s at path %s\n",
							input.getValue(), inputToCenterPath);
				} else {
					/*
					 * this center is the best match for this subtree.
					 */

					EntityLeaf center = pfuser.fuse(centers);
					pfuser.setCenterNode(center);

					// System.out.println("fuse to node "
					// + center.getValue().getValue());

					if (center.getFusionProbability() > threshold) {
						ftuple = new FusionTuple(subroot, entityTree, center,
								subtreeMatch);

						ftuple.setProbability(center.getFusionProbability());
						// fusionResults.add(ftuple);
					}
				}

			}

			if (ftuple != null)
				ftuple.appendChild();

			break;

		default:

			break;

		}

		fuser.instantiate(facts, subroot, factItemCounter.valueSet());
		fuser.instantiate(facts, subroot, tempFactItemCounter.valueSet());

		return facts;
	}

	private void normalize() {
		Map<String, Double> map;

		map = factItemCounter.getNormalizedMap();

		for (Entry<String, Double> ent : map.entrySet()) {
			String key = ent.getKey();
			FactItem item = (FactItem) factItemCounter.getValue(key);

			item.setPercentage(ent.getValue());
		}

		map = tempFactItemCounter.getNormalizedMap();

		for (Entry<String, Double> ent : map.entrySet()) {
			String key = ent.getKey();
			FactItem item = (FactItem) tempFactItemCounter.getValue(key);

			item.setPercentage(ent.getValue());
		}
	}

	public void setCenterNode(EntityLeaf centerNode) {
		this.centerNode = centerNode;
	}

	public void setEntityTree(EntityTree entityTree) {
		this.entityTree = entityTree;
		inputNode = entityTree.getConcreteRoot();

		if (matchProperty.isExistsMatch()) {
			setCenterNode(inputNode);
			inputToCenterPath = null;
			fuser.setInputEqualsToCenter(true);
		}
	}

	public void setMatchProperty(MatchProperty matchProperty) {
		this.matchProperty = matchProperty;

		if (!matchProperty.isExistsMatch()) {
			inputToCenterPath = matchProperty.getPredicatePath();

			if (inputToCenterPath.isEmpty()
					|| inputToCenterPath.getFirstPredicate().getValue()
							.equals(PredicatePath.INPUT_PATH))
				fuser.setInputEqualsToCenter(true);
			else
				fuser.setInputEqualsToCenter(false);
		}
	}

	public void setSubtreeMatch(SubtreeMatch subtreeMatch) {
		this.subtreeMatch = subtreeMatch;
	}

	public HtmlTable toFactItemHtmlTable() {
		TableData data = new TableData();
		TableTuple tuple = new TableTuple();

		tuple.add("Input to Center Path");
		tuple.add("Center to Literal Path");
		tuple.add("XmlPath");
		tuple.add("Facts");
		tuple.add("Percentage");
		tuple.add("Count");
		data.add(tuple);

		for (String key : factItemCounter.keySet()) {
			tuple = new TableTuple();
			FactItem item = (FactItem) factItemCounter.getValue(key);

			if (item.getInputToCenterPath() != null)
				tuple.add(item.getInputToCenterPath().getKey());
			else
				tuple.add("NULL");

			tuple.add(item.getCenterToValulePath().getKey());
			tuple.add(item.getXmlLeaf().getXpath().getValue());
			tuple.add(item.getFactGroup().toString("<br>"));
			tuple.add(String.format("%.4f", item.getPercentage()));
			tuple.add(String.format("%d / %d", factItemCounter.get(key),
					factItemCounter.getNormalizeFactor()));

			data.add(tuple);
		}

		data.sort(4);

		return new HtmlTable("Fact Item Frequency", data, false);
	}

	public HtmlTable toTmpFactItemHtmlTable() {
		TableData data = new TableData();
		TableTuple tuple = new TableTuple();

		tuple.add("XmlPath");
		tuple.add("Predicate");
		tuple.add("Facts");
		tuple.add("Percentage");
		tuple.add("Count");
		data.add(tuple);

		for (String key : tempFactItemCounter.keySet()) {
			tuple = new TableTuple();
			FactItem item = (FactItem) tempFactItemCounter.getValue(key);

			tuple.add(item.getXmlLeaf().getXpath().getValue());
			tuple.add(item.getTempPredicate().getValue());
			tuple.add(item.getFactGroup().toString("<br>"));
			tuple.add(String.format("%.4f", item.getPercentage()));
			tuple.add(String.format("%d / %d", tempFactItemCounter.get(key),
					tempFactItemCounter.getNormalizeFactor()));

			data.add(tuple);
		}

		data.sort(3);

		return new HtmlTable("Temperary Fact Item Frequency", data, false);
	}

	public String toXmlToPredicateDebugString() {
		MapSet<String, String> mapSet = new MapSet<>();

		FactItem item;
		String pred;
		XmlPath rootPath = dataGuide.getSelectedNode().getXpath();

		for (String key : factItemCounter.keySet()) {
			item = (FactItem) factItemCounter.getValue(key);

			pred = item.getCenterToValulePath().getKey();

			XmlPath xpath = item.getXmlLeaf().getXpath();
			XmlPath relPath = xpath.getRelativePathsTo(rootPath);

			mapSet.put(pred, relPath == null ? xpath.stripIndex() : "///"
					+ relPath.getValue());
		}

		StringBuilder sbuilder = new StringBuilder();

		sbuilder.append(String.format("// %s\r\n\r\n", matchProperty.toString()));

		for (Entry<String, Set<String>> ee : mapSet.entrySet()) {
			sbuilder.append(ee.getKey());
			sbuilder.append("\r\n");

			for (String s : ee.getValue()) {
				sbuilder.append(s);
				sbuilder.append("\r\n");
			}

			sbuilder.append("\r\n");
		}

		return sbuilder.toString();
	}

	public String toXmlToPredicateString() {
		StringBuilder sbuilder = new StringBuilder();
		XmlPath rootPath = dataGuide.getSelectedNode().getXpath();

		for (String key : factItemCounter.keySet()) {
			FactItem item = (FactItem) factItemCounter.getValue(key);

			sbuilder.append(String.format("%.4f", item.getPercentage()));
			sbuilder.append(", ");
			sbuilder.append(String.format("%d/%d", factItemCounter.get(key),
					factItemCounter.getNormalizeFactor()));
			sbuilder.append(", ");
			sbuilder.append(item.getCenterToValulePath().getKey());
			sbuilder.append(", ");

			XmlPath xpath = item.getXmlLeaf().getXpath();
			XmlPath relPath = xpath.getRelativePathsTo(rootPath);

			sbuilder.append(relPath == null ? xpath.stripIndex() : "///"
					+ relPath.getValue());

			sbuilder.append("\r\n");
		}

		return sbuilder.toString();
	}

}
